﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EnvDTE;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.ComponentModelHost;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Shell;
using Util;

namespace RoslynProcess
{
    public delegate void OnBeforeDocumentWindowShow(Document document, int isFirstShow, IVsWindowFrame vsWindowFrame);
    public delegate void OnAfterDocumentWindowHide(Document document, IVsWindowFrame vsWindowFrame);
    public delegate void OnAfterSave(Document document);

    class RunningDocumentTableHandler : IVsRunningDocTableEvents, IDisposable
    {
        private readonly DTE dte;
        private uint runningDocumentTableCookie;

        public event OnBeforeDocumentWindowShow OnBeforeDocumentWindowShow;
        public event OnAfterDocumentWindowHide OnAfterDocumentWindowHide;
        public event OnAfterSave OnAfterSave;

        public RunningDocumentTableHandler(DTE dte)
        {
            this.dte = dte;

            InitializeRunningDocumentTable();
        }

        #region Helpers - Initialize and Dispose IVsRunningDocumentTable

        private IVsRunningDocumentTable runningDocumentTable;
        private IVsRunningDocumentTable RunningDocumentTable
        {
            get
            {
                if (runningDocumentTable == null)
                {
                    runningDocumentTable = VSUtil.GetRunningDocumentTable();
                }

                return runningDocumentTable;
            }
        }

        private void InitializeRunningDocumentTable()
        {
            if (RunningDocumentTable != null)
            {
                RunningDocumentTable.AdviseRunningDocTableEvents(
                    this, out runningDocumentTableCookie);
            }
        }

        void IDisposable.Dispose()
        {
            if (runningDocumentTableCookie != 0)
            {
                runningDocumentTable.UnadviseRunningDocTableEvents(runningDocumentTableCookie);
                runningDocumentTableCookie = 0;
            }
        }
        #endregion

        // Copied from http://social.msdn.microsoft.com/Forums/is/vsx/thread/2b5fcbd9-ddc9-42c9-b04e-67bd2aa4beb7
        private string GetDocumentMoniker(uint docCookie)
        {
            uint rdtFlags, readLocks, editLocks, itemId;
            IVsHierarchy owningHierarchy;
            string documentMoniker;
            IntPtr punkDocData;
            if (Microsoft.VisualStudio.ErrorHandler.Succeeded(RunningDocumentTable.GetDocumentInfo(docCookie,
                                                        out rdtFlags,
                                                        out readLocks,
                                                        out editLocks,
                                                        out documentMoniker,
                                                        out owningHierarchy,
                                                        out itemId,
                                                        out punkDocData)))
            {
                try
                {
                    return documentMoniker;
                }
                finally
                {
                    if (punkDocData != IntPtr.Zero)
                    {
                        //It is important to release this, it is an IntPtr that represents a COM object, pursuant to the rules of COM
                        //(since this is an out parameter) it has had its ref-count increased by 1, which means if we don't call Release
                        //on it we will cause it to leak (and anything it holds on to).
                        System.Runtime.InteropServices.Marshal.Release(punkDocData);
                    }
                }
            }

            return null;
        }

        // Copied from https://bitbucket.org/s_cadwallader/codemaid/raw/8b5703e18ecc/CodeMaid/Events/RunningDocumentTableEventListener.cs
        /// <summary>
        /// Gets the document object from a document cookie.
        /// </summary>
        /// <param name="docCookie">The document cookie.</param>
        /// <returns>The document object, otherwise null.</returns>
        private Document GetDocumentFromCookie(uint docCookie)
        {
            // Retrieve document information from the cookie to get the full document name.
            string documentName = GetDocumentMoniker(docCookie);

            // Search against the IDE documents to find the object that matches the full document name.
            return dte
                    .Documents
                    .OfType<Document>()
                    .FirstOrDefault(x => x != null && x.FullName == documentName);
        }

        #region IVsRunningDocTableEvents
        // Handle the case where the user opens a new code document / switches to a different code document.
        int IVsRunningDocTableEvents.OnBeforeDocumentWindowShow(uint docCookie, int isFirstShow, IVsWindowFrame vsWindowFrame)
        {
            if (isFirstShow == 0 && OnBeforeDocumentWindowShow != null)
            {
                OnBeforeDocumentWindowShow(GetDocumentFromCookie(docCookie), isFirstShow, vsWindowFrame);
            }

            return VSConstants.S_OK;
        }

        // Handle the case where the user closes the current code document / switches to a different code document.
        int IVsRunningDocTableEvents.OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame vsWindowFrame)
        {
            if (OnAfterDocumentWindowHide != null)
            {
                OnAfterDocumentWindowHide(GetDocumentFromCookie(docCookie), vsWindowFrame);
            }

            return VSConstants.S_OK;
        }

        #region Unused IVsRunningDocTableEvents Events
        int IVsRunningDocTableEvents.OnBeforeLastDocumentUnlock(uint docCookie, uint lockType, uint readLocksRemaining, uint editLocksRemaining)
        {
            return VSConstants.S_OK;
        }

        int IVsRunningDocTableEvents.OnAfterAttributeChange(uint docCookie, uint grfAttribs)
        {
            return VSConstants.S_OK;
        }

        int IVsRunningDocTableEvents.OnAfterFirstDocumentLock(uint docCookie, uint lockType, uint readLocksRemaining, uint editLocksRemaining)
        {
            return VSConstants.S_OK;
        }

        int IVsRunningDocTableEvents.OnAfterSave(uint docCookie)
        {
            if (OnAfterSave != null)
            {
                OnAfterSave(GetDocumentFromCookie(docCookie));
            }

            return VSConstants.S_OK;
        }
        #endregion
        #endregion

    }
}